﻿using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace FirstFloor.Documents.Presentation.Behaviors
{
    /// <summary>
    /// Encapsulates the behavior for selecting text with the mouse.
    /// </summary>
    public class MouseSelectionBehavior
        : Behavior<FrameworkElement>
    {
        /// <summary>
        /// Identifies the IsMouseEnabled dependency property.
        /// </summary>
        public static readonly DependencyProperty IsMouseEnabledProperty = DependencyProperty.Register("IsMouseEnabled", typeof(bool), typeof(MouseSelectionBehavior), new PropertyMetadata(true, OnIsMouseEnabledChanged));
        /// <summary>
        /// Identifies the TextContainer dependency property.
        /// </summary>
        public static readonly DependencyProperty TextContainerProperty = DependencyProperty.Register("TextContainer", typeof(ITextContainer), typeof(MouseSelectionBehavior), null);

        private bool isSelecting;

        private static void OnIsMouseEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((MouseSelectionBehavior)o).OnIsMouseEnabledChanged((bool)e.NewValue);
        }

        private void OnIsMouseEnabledChanged(bool newValue)
        {
            StopCurrentMouseOperation();

            if (this.AssociatedObject != null) {
                this.AssociatedObject.Cursor = newValue ? Cursors.IBeam : null;
            }
        }

        /// <summary>
        /// Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.MouseLeftButtonDown += OnMouseLeftButtonDown;
            this.AssociatedObject.MouseLeftButtonUp += OnMouseLeftButtonUp;
            this.AssociatedObject.MouseMove += OnMouseMove;
            this.AssociatedObject.LostMouseCapture += OnLostMouseCapture;

            // update cursor
            OnIsMouseEnabledChanged(this.IsMouseEnabled);
        }

        /// <summary>
        /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            this.AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
            this.AssociatedObject.MouseLeftButtonUp -= OnMouseLeftButtonUp;
            this.AssociatedObject.MouseMove -= OnMouseMove;
            this.AssociatedObject.LostMouseCapture -= OnLostMouseCapture;
        }

        /// <summary>
        /// Gets or sets a value indicating whether the behavior handles mouse events.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the mouse is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool IsMouseEnabled
        {
            get { return (bool)GetValue(IsMouseEnabledProperty); }
            set { SetValue(IsMouseEnabledProperty, value); }
        }

        /// <summary>
        /// Gets or sets the text container.
        /// </summary>
        /// <value>The text container.</value>
        /// <remarks>
        /// <para>
        /// The text container is used to perform internal link navigation.
        /// </para>
        /// </remarks>
        public ITextContainer TextContainer
        {
            get { return (ITextContainer)GetValue(TextContainerProperty); }
            set { SetValue(TextContainerProperty, value); }
        }


        private void StopCurrentMouseOperation()
        {
            this.AssociatedObject.ReleaseMouseCapture();
            this.isSelecting = false;
        }

        private TextPointer GetPositionFromMouse(MouseEventArgs e)
        {
            var element = (FrameworkElement)e.OriginalSource;

            // find the ancestor list for the fixed page instance
            var fixedPage = element.AncestorsAndSelf().OfType<FixedPage>().FirstOrDefault();
            if (fixedPage != null) {
                var position = e.GetPosition(fixedPage);
                return fixedPage.GetPositionFromPoint(position);
            }
            return null;
        }

        private bool IsTextSelectionEnabled
        {
            get
            {
                return this.TextContainer != null && this.TextContainer.Selection != null && this.TextContainer.Selection.IsEnabled;
            }
        }

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.IsMouseEnabled && this.IsTextSelectionEnabled) {
                var position = GetPositionFromMouse(e);
                if (position != null) {
                    this.TextContainer.Selection.Select(position, position);
                    this.isSelecting = true;
                    this.AssociatedObject.CaptureMouse();
                }
            }
        }

        private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            StopCurrentMouseOperation();
        }

        private void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            StopCurrentMouseOperation();
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (this.isSelecting) {
                var position = GetPositionFromMouse(e);
                if (position != null) {
                    var selection = this.TextContainer.Selection;
                    selection.Select(selection.Start, position);
                }
            }
        }
    }
}
